﻿namespace JoinBox.Basal;


using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;


// 保存剪贴板内容,在使用后恢复
// .NET框架和Windows.Forms.Clipboard该类仅允许处理可序列化的数据
// 那么,如果剪贴板包含来自不兼容应用程序的数据怎么办?简单!.NET Framework认为剪贴板为空。
// 要读取和处理这些数据,我们需要调用user32.dll和kernerl32.dll。
public class ClipTool
{
    /// <summary>
    /// 指定数据按照指定格式放入剪切板中
    /// https://baike.baidu.com/item/SetClipboardData/3266114
    /// </summary>
    /// <param name="Format">用来指定要放到剪切板中的数据的格式</param>
    /// <param name="hData">指定具有指定格式的数据的句柄,该参数可以是空</param>
    /// <returns></returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SetClipboardData(uint Format, IntPtr hData);
    /// <summary>
    /// 枚举剪贴板内数据类型
    /// </summary>
    /// <param name="format"></param>
    /// <returns></returns>
    [DllImport("user32.dll", SetLastError = true)]
    static extern uint EnumClipboardFormats(uint format);
    /// <summary>
    /// 打开剪贴板查看
    /// 如果另一个窗口已经打开剪贴板,函数会失败.每次成功调用后都应有CloseClipboard调用.
    /// </summary>
    /// <param name="hWndNewOwner">为NULL的话,则打开剪贴板与当前任务相关</param>
    /// <returns>如果函数执行成功,返回非零值.</returns>
    [DllImport("user32.dll", SetLastError = true)]
    static extern int OpenClipboard(IntPtr hWndNewOwner);
    /// <summary>
    /// 打开剪贴板并获取剪贴板内容
    /// </summary>
    /// <param name="uFormat"></param>
    /// <returns></returns>
    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr GetClipboardData(uint uFormat);
    /// <summary>
    /// 关闭剪贴板,这使其他窗口或程序能访问剪贴板
    /// </summary>
    /// <returns></returns>
    [DllImport("user32.dll", SetLastError = true)]
    static extern int CloseClipboard();
    /// <summary>
    /// 锁定内存块
    /// </summary>
    /// <param name="hMem">指定的内存指针</param>
    /// <returns>返回一个地址值</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern int GlobalLock(IntPtr hMem);
    /// <summary>
    /// 解锁内存块
    /// </summary>
    /// <param name="hMem">指定的内存指针</param>
    /// <returns>返回一个地址值</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern int GlobalUnlock(IntPtr hMem);
    /// <summary>
    /// 获取内存块大小
    /// </summary>
    /// <param name="hMem"></param>
    /// <returns></returns>
    [DllImport("kernel32.dll")]
    public static extern uint GlobalSize(IntPtr hMem);

    /// <summary>
    /// 剪贴板数据保存目标数据列表
    /// </summary>
    static readonly List<byte[]> _bytes = new();
    /// <summary>
    /// 剪贴板数据类型列表
    /// </summary>
    static readonly List<uint> _formats = new();

    /// <summary>
    /// 保存剪贴板内容,返回真为成功,false为失败。
    /// </summary>
    /// <returns></returns>
    public static bool SaveClip()
    {

        if (OpenClipboard(IntPtr.Zero) < 1)
            return false;

        _bytes.Clear();
        _formats.Clear();

        uint uFormat = 0;
        while (true)
        {
            uFormat = EnumClipboardFormats(uFormat);// 枚举剪贴板所有数据类型
            if (uFormat > 0)
            {
                _formats.Add(uFormat);
                IntPtr hMem = GetClipboardData(uFormat);
                GlobalLock(hMem);
                if (hMem != (IntPtr)0)
                {
                    uint size = GlobalSize(hMem);
                    IntPtr s = (IntPtr)GlobalLock(hMem);
                    byte[] buffer = new byte[(int)size];
                    if (size > 0)
                    {
                        Marshal.Copy(s, buffer, 0, (int)size);// 将剪贴板数据保存到自定义字节数组
                    }
                    GlobalUnlock(hMem);
                    _bytes.Add(buffer);
                }
            }
            else
            {
                break;
            }
        }
        CloseClipboard();
        return _formats.Count > 0;
    }

    /// <summary>
    /// 恢复保存的数据
    /// </summary>
    /// <returns></returns>
    public static bool RestoreClip()
    {
        if (_formats.Count <= 0)
            return false;
        if (OpenClipboard(IntPtr.Zero) < 1)
            return false;

        for (int i = 0; i < _formats.Count; i++)
        {
            int size = _bytes[i].Length;
            IntPtr si = Marshal.AllocHGlobal(size);
            if (size > 0)
                Marshal.Copy(_bytes[i], 0, si, size);
            SetClipboardData(_formats[i], si);
        }
        CloseClipboard();
        return _formats.Count > 0;
    }

    /// <summary>
    /// 将CWnd加入一个窗口链,每当剪贴板的内容发生变化时,就会通知这些窗口
    /// </summary>
    /// <param name="hWndNewViewer">句柄</param>
    /// <returns>返回剪贴板观察器链中下一个窗口的句柄</returns>
    [DllImport("User32.dll")]
    public static extern int SetClipboardViewer(IntPtr hWndNewViewer);

    /// <summary>
    /// 从剪贴板链中移出的窗口句柄
    /// </summary>
    /// <param name="hWndRemove">从剪贴板链中移出的窗口句柄</param>
    /// <param name="hWndNewNext">hWndRemove的下一个在剪贴板链中的窗口句柄</param>
    /// <returns>如果成功,非零;否则为0。</returns>
    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
}